;================================================================
;  jump.s
;               The Jumping Ball
;
;================================================================
;
; 25thanni, a demo dedicated to the 25th anniversary of the ZX81.
;
; (c)2006 Bodo Wenzel
;
; This program is free software; you can redistribute it and/or
; modify it under the terms of the GNU General Public License as
; published by the Free Software Foundation; either version 2 of
; the License, or (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public
; License along with this program; if not, write to the Free
; Software Foundation Inc., 59 Temple Place, Suite 330, Boston,
; MA 02111-1307 USA
;================================================================

	.module	jump

;= Externals ====================================================

	.globl	heap_ptr
	.globl	hrg_file
	.globl	frames

	.globl	FRAME_RATE
	.globl	M_TOP,M_BOTTOM
	.globl	check_break
	.globl	set_show
	.globl	show_dummy
	.globl	vsync
	.globl	b_check

	.globl	CHAR_WIDTH,CHAR_HEIGHT
	.globl	G0,G1,G2,G3,G4,G5,G6,G7,G8,G9,GA,GB,GC,GD,GE,GF
	.globl	__,X8,X9,XA,QU,PD,DL,CL,QM,LP,RP,GT,LT,EQ,PL,MI
	.globl	TI,SL,SC,CM,PE,_0,_1,_2,_3,_4,_5,_6,_7,_8,_9
	.globl	_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P
	.globl	_Q,_R,_S,_T,_U,_V,_W,_X,_Y,_Z,NL,INV
	.globl	render_char

	.globl	HRG_WIDTH,HRG_HEIGHT,PIXEL_PER_CLOCK

	.globl	DELAY_OFFSET
	.globl	delay

	.globl	MATH_VALUE_SIZE
	.globl	quotient
	.globl	sine
	.globl	arccos

	.globl	unpack

;= Constants ====================================================

; double global definition checked by linker
FRAME_RATE	==	50
HRG_WIDTH	==	256
HRG_HEIGHT	==	192
PIXEL_PER_CLOCK	==	2
CHAR_WIDTH	==	8
CHAR_HEIGHT	==	8
MATH_VALUE_SIZE	==	7

BITS_PER_BYTE	=	8

PIC_COUNT	=	32
PIC_WIDTH	=	32	; must be a power of 2
PIC_HEIGHT	=	32
SHADOW_HEIGHT	=	PIC_HEIGHT/4
PIC_BYTE_WIDTH	=	PIC_WIDTH/BITS_PER_BYTE
MAP_HEIGHT	=	CHAR_HEIGHT
MAP_WIDTH	=	CHAR_WIDTH*2
MAP_BYTE_WIDTH	=	MAP_WIDTH/BITS_PER_BYTE
BMP_HEIGHT	=	CHAR_HEIGHT
BMP_WIDTH	=	CHAR_WIDTH*8	; must be power of 2
BMP_BYTE_WIDTH	=	BMP_WIDTH/BITS_PER_BYTE

V_AMPLITUDE	=	HRG_HEIGHT-PIC_HEIGHT-SHADOW_HEIGHT
H_AMPLITUDE	=	(HRG_WIDTH-PIC_WIDTH)/PIXEL_PER_CLOCK

BREAK_TIMEOUT	=	3*FRAME_RATE

;= Program code =================================================

	.area	CODE

;- Show the Jumping Ball ----------------------------------------

jump::
	ld	hl,#show_dummy
	call	set_show

	ld	hl,(heap_ptr)
	ld	(hrg_file),hl
	ld	de,#(PIC_COUNT+1)*PIC_HEIGHT*PIC_BYTE_WIDTH
	add	hl,de
	ld	(bmp),hl
	ld	de,#BMP_HEIGHT*BMP_BYTE_WIDTH
	add	hl,de
	ld	(table),hl

	ld	hl,(bmp)
	ld	c,#BMP_BYTE_WIDTH
	ld	de,#bmp_text
	ld	b,c
j_b_lp:
	ld	a,(de)
	inc	de
	call	render_char
	inc	hl
	djnz	j_b_lp		; render bitmap

;- create table of masks to render all animation pictures - - - -

; each row:
; byte			content
; 0			offset into bitmap
; 1..PIC_WIDTH		masks for bitmap

; HL	upper left quarter
; BC'	upper right quarter
; DE'	lower left quarter
; HL'	lower right quarter

	ld	hl,(table)
	ld	bc,#1+PIC_WIDTH-1
	add	hl,bc
	ld	c,l
	ld	b,h
	ld	de,#1+(PIC_HEIGHT-2)*(1+PIC_WIDTH)
	add	hl,de
	ex	de,hl
	ld	hl,#1+PIC_WIDTH-1
	add	hl,de
	exx
	ld	hl,(table)

	; Each row of the picture is mapped to a row of the
	; source map, only one half needs calculation:
	; map_row = map_height / PI * arc cos(
	;             picture_height / 2 - picture_row)
Y_STEP	=	(1<<MATH_VALUE_SIZE)/(PIC_HEIGHT/2)
	ld	c,#(PIC_HEIGHT/2-1)*Y_STEP+Y_STEP/2
j_t_ylp:
	ld	a,c
	call	arccos
	ld	e,a		; 0 .. 63 = 0 .. PI/2-0.02

	.if	MAP_HEIGHT-8
	.error	"Code depends on MAP_HEIGHT=8"
	.endif
	.if	BMP_WIDTH-64
	.error	"Code depends on BMP_WIDTH=64"
	.endif
	rrca
	and	#(BMP_HEIGHT/2-1)*BMP_BYTE_WIDTH
				; => map row 0 .. 3
	ld	(hl),a
	inc	hl		; bitmap row offset (upper half)
	neg
	add	#(BMP_HEIGHT-1)*BMP_BYTE_WIDTH
	exx
	ld	(de),a
	inc	de
	exx			; bitmap row offset (lower half)

	; Each pixel of the picture is mapped to a bit number of
	; the source map, only one half needs calculation:
	; map_bit = map_width / PI * arc cos(
	;             (picture_width / 2 - picture_column)
	;             / sin(map_row / map_height * PI)
	ld	a,e
	call	sine		; 0 .. 127 = 0 .. 0.99
	ld	e,a

X_STEP	=	(1<<MATH_VALUE_SIZE)/(PIC_WIDTH/2)
	ld	b,#(PIC_WIDTH/2-1)*X_STEP+X_STEP/2
j_t_xlp:
	xor	a
	ld	(hl),a
	exx
	ld	(bc),a
	ld	(de),a
	ld	(hl),a
	exx			; clear mask

	ld	a,b
	cp	e
	jr	nc,j_t_next	; not on sphere?

	call	quotient	; 0 .. 127 = 0 .. 0.99
	call	arccos		; 0 .. 63 = 0 .. PI/2-0.02

	.if	MAP_WIDTH-16
	.error	"Code depends on MAP_WIDTH=16"
	.endif
	rrca
	rrca
	rrca
	and	#BITS_PER_BYTE-1; => map bit number 0 .. 7

	inc	a
	ld	d,a
	push	de
	ld	a,#1
j_t_bl_lp:
	rrca
	dec	d
	jr	nz,j_t_bl_lp
	ld	(hl),a
	exx
	ld	(de),a		; bitmap mask (left half)
	exx
	pop	de
	ld	a,#1<<(BITS_PER_BYTE-1)
j_t_br_lp:
	rlca
	dec	d
	jr	nz,j_t_br_lp
	exx
	ld	(bc),a
	ld	(hl),a
	exx			; bitmap mask (right half)

j_t_next:
	inc	hl
	exx
	dec	bc
	inc	de
	dec	hl
	exx			; advance pointers for one pixel

	ld	a,b
	sub	#X_STEP
	ld	b,a
	jr	nc,j_t_xlp

	ld	de,#PIC_WIDTH/2
	add	hl,de
	exx
	ld	hl,#PIC_WIDTH/2+1+PIC_WIDTH
	add	hl,bc
	ld	c,l
	ld	b,h
	ld	hl,#-(1+PIC_WIDTH/2+1+PIC_WIDTH)
	add	hl,de
	ex	de,hl
	ld	hl,#1+PIC_WIDTH-1
	add	hl,de
	exx			; advance pointers for one row

	ld	a,c
	sub	#Y_STEP
	ld	c,a
	jr	nc,j_t_ylp

;- render all animation pictures  - - - - - - - - - - - - - - - -

	ld	hl,(hrg_file)
	exx
	ld	c,#PIC_COUNT
j_r_loop:
	call	render_pic

	ld	hl,(bmp)
	ld	de,#BMP_HEIGHT*BMP_BYTE_WIDTH
	add	hl,de
	ld	b,e
j_r_sf_lp:
	dec	hl
	rl	(hl)
	djnz	j_r_sf_lp	; shift bitmap one pixel

	dec	c
	jr	nz,j_r_loop

	ld	hl,(bmp)
	ld	b,#BMP_HEIGHT*BMP_BYTE_WIDTH
	ld	a,#~0
j_r_clr_lp:
	ld	(hl),a
	inc	hl
	djnz	j_r_clr_lp	; blacken bitmap for shadow

	exx
	ld	(shadow),hl
	exx
	call	render_pic

	ld	hl,(shadow)
	ld	e,l
	ld	d,h
	ld	bc,#(PIC_HEIGHT/SHADOW_HEIGHT/2-1)*PIC_BYTE_WIDTH
	add	hl,bc
	ld	a,#SHADOW_HEIGHT
j_r_sr_lp:
	ld	bc,#PIC_BYTE_WIDTH
	ldir
	ld	bc,#(PIC_HEIGHT/SHADOW_HEIGHT-1)*PIC_BYTE_WIDTH
	add	hl,bc
	dec	a
	jr	nz,j_r_sr_lp	; make shadow flat

;- ready to start - - - - - - - - - - - - - - - - - - - - - - - -

	xor	a
	ld	(sh_p_hori),a

	ex	de,hl
	ld	(show_hrg),hl
	ld	de,#scroll_line
	ld	bc,#PAC_SL_SIZE
	call	unpack

	ld	hl,#move_set
	ld	(hl),a		; vertical position upper byte
	inc	hl
	ld	(hl),a		; vertical position lower byte
	inc	hl
	ld	(hl),a		; vertical speed
	inc	hl
	ld	(hl),#+1	; horizontal speed
	inc	hl
	ld	(hl),#1		; index of current picture

	ld	hl,#show_hrg
	call	set_show

;- the main loop  - - - - - - - - - - - - - - - - - - - - - - - -

j_loop:
	ld	hl,#move_set+2

	; the vertical move is a parabolic curve
	ld	a,(hl)
	inc	a
	ld	(hl),a
	dec	hl
	ld	c,a
	rla
	sbc	a,a
	ld	b,a
	ld	a,(hl)
	add	a,c
	ld	(hl),a
	dec	hl
	ld	c,a
	ld	a,(hl)
	adc	a,b
	ld	(hl),a
	inc	hl
	inc	hl
	rra
	rr	c
	rra
	rr	c
	rra
	rr	c
	ld	a,c
	cp	#V_AMPLITUDE
	jr	c,j_v_set
	ld	c,#V_AMPLITUDE
	ld	a,(hl)
	inc	a
	neg
	ld	(hl),a
j_v_set:
	inc	hl
	ld	a,#M_TOP-2
	add	a,c
	ld	(sh_p_top),a
	ld	a,#V_AMPLITUDE
	sub	c
	ld	(sh_p_vert),a

	; the horizontal move is a linear curve
	ld	de,#sh_p_hori
	ld	a,(de)
	add	a,(hl)
	ld	(de),a
	jr	z,j_h_flip
	cp	#H_AMPLITUDE
	jr	c,j_h_quit
j_h_flip:
	ld	a,(hl)
	neg
	ld	(hl),a
j_h_quit:

	; select next picture
	ld	a,(frames+0)
	rra
	ld	a,(hl)
	inc	hl
	jr	nc,j_no_anim	; slow down rotation a bit
	rla
	ld	a,(hl)
	jr	nc,j_a_right
	ld	de,#-PIC_HEIGHT*PIC_BYTE_WIDTH
	dec	a
	jr	nz,j_a_store
	ld	de,#(PIC_COUNT-1)*PIC_HEIGHT*PIC_BYTE_WIDTH
	ld	a,#PIC_COUNT
	jr	j_a_store
j_a_right:
	ld	de,#PIC_HEIGHT*PIC_BYTE_WIDTH
	inc	a
	cp	#PIC_COUNT+1
	jr	nz,j_a_store
	ld	de,#-(PIC_COUNT-1)*PIC_HEIGHT*PIC_BYTE_WIDTH
	ld	a,#1
j_a_store:
	ld	(hl),a
	ld	hl,(hrg_file)
	add	hl,de
	ld	(hrg_file),hl
j_no_anim:

	call	check_break
	jr	nc,j_abort

	call	b_check
	ret	z		; bottom scroller not finished?

	call	vsync

	jp	j_loop

j_abort:
	ld	b,#BREAK_TIMEOUT
j_ab_lp:
	call	vsync
	call	check_break
	ret	c
	djnz	j_ab_lp		; wait for key release
	ret

;- Render a picture via the mask table and the bitmap -----------
; HL'	pointer to picture to render

render_pic:
	ld	de,(table)
	ld	b,#PIC_HEIGHT
rp_pic_loop:
	ld	hl,(bmp)
	ld	a,(de)
	inc	de
	add	a,l
	ld	l,a
	jr	nc,rp_ptr_ok
	inc	h
rp_ptr_ok:
	exx

	ld	c,#PIC_BYTE_WIDTH
rp_row_loop:
	ld	de,#1<<(BITS_PER_BYTE-1)
	ld	b,#BITS_PER_BYTE
rp_pixel_lp:
	exx
	ld	a,(de)		; mask from table
	inc	de
	and	(hl)		; pattern of bitmap
	exx
	jr	z,rp_white
	ld	a,d
	or	e
	ld	d,a
rp_white:
	rrc	e
	djnz	rp_pixel_lp

	.if	MAP_WIDTH-16
	.error	"Code depends on MAP_WIDTH=16"
	.endif
	.if	PIC_WIDTH-32
	.error	"Code depends on PIC_WIDTH=32"
	.endif
	bit	0,c
	jr	z,rp_row_next
	exx
	inc	hl
	exx
rp_row_next:
	ld	(hl),d		; set pixels in picture
	inc	hl
	dec	c
	jr	nz,rp_row_loop

	exx
	djnz	rp_pic_loop

	ret

;- HRG screen routine -------------------------------------------

show_hrg:
	.dw	0
sh_p_top	=	.
	.db	M_TOP-2
	.db	M_BOTTOM

sh_p_hori	= .+1
	ld	a,#0
	call	delay		; horizontal delay
	ld	a,#195+DELAY_OFFSET
	call	delay		; adjustment

	ld	c,#2		; the ball and its shadow

	ld	hl,(hrg_file)
	ld	b,#PIC_HEIGHT
sh_loop:
	ld	a,#6		; 7
sh_again:
	dec	a		; 4
	jr	nz,sh_again	; 12/7
	ret	nz		; 5, dummy
	ex	de,hl		; 4
	ld	hl,#sh_next	; 10

	jp	.+0x8003	; 10
	ld	a,d		; 4
	ld	i,a		; 9
	ld	a,e		; 4
	ld	r,a		; 9
	.db	0,0,0,0		; 4*4
	jp	(hl)		; 4
sh_next:
	ld	hl,#PIC_BYTE_WIDTH; 10
	add	hl,de		; 11
	djnz	sh_loop		; 13/8

	dec	c		; 4
	jr	z,sh_quit	; 7

sh_p_vert	=	.+1
	ld	a,#V_AMPLITUDE	; 7
	ld	b,a		; 4
	and	a		; 4
	jr	z,sh_no_gap	; 12/7
	ret	z		; 5, dummy
sh_gap_lp:
	ld	a,#207-23+DELAY_OFFSET ; 7
	call	delay		; #
	dec	b		; 4
	jr	nz,sh_gap_lp	; 12/7 = 23+delay
	ret	nz		; 5, dummy
sh_no_gap:

	ld	hl,(shadow)	; 16
	ld	b,#SHADOW_HEIGHT; 7

	inc	de		; 6 (dummy)
	inc	de		; 6 (dummy)
	ld	a,#1		; 7
	jr	sh_again	; 12 = +87+n*207

sh_quit:
	ex	(sp),hl		; protection against
	ex	(sp),hl		; premature NMI
	ret

;= Data =========================================================

; Text to render for the map, repeated at least MAP_WIDTH pixels:
bmp_text:
	.db	_Z,_X,_8,_1,_Z,_X,__,__
	.if	.-bmp_text-BMP_BYTE_WIDTH
	.error	"Bitmap text has wrong size"
	.endif

scroll_line:
	.include	"jump_sl.inc"
PAC_SL_SIZE	=	.-scroll_line
UNP_SL_SIZE	=	UNPACKED

;= Variables ====================================================

	.area	SCRATCH	(ovr)

shadow:
	.dw	0
bmp:
	.dw	0
table:
	.dw	0

move_set:
	.ds	2		; vertical position
	.ds	1		; vertical speed
	.ds	1		; horizontal speed
	.ds	1		; index of current picture

;= Heap usage ===================================================

;		animation pictures
HEAP =		PIC_COUNT*PIC_HEIGHT*PIC_BYTE_WIDTH
;		shadow picture
HEAP =	HEAP +	SHADOW_HEIGHT*PIC_BYTE_WIDTH

;		rest of unflatten shadow picture
HEAP1 =	HEAP +	(PIC_HEIGHT-SHADOW_HEIGHT)*PIC_BYTE_WIDTH
;		base bitmap
HEAP1 =	HEAP1 +	BMP_HEIGHT*BMP_BYTE_WIDTH
;		table of offsets and masks
HEAP1 =	HEAP1 +	PIC_HEIGHT*(1+PIC_WIDTH)

;		unpacked scroll text
HEAP2 =	HEAP +	UNP_SL_SIZE

	.area	HEAP	(abs,ovr)
	.if	HEAP1/HEAP2
	.ds	HEAP1
	.else
	.ds	HEAP2
	.endif

;= The end ======================================================
